/**
 * 
 * Copyright (C) 2011 Cody Stoutenburg . All rights reserved.
 *
 *       This program is free software; you can redistribute it and/or
 *       modify it under the terms of the GNU Lesser General Public License
 *       as published by the Free Software Foundation; either version 2.1
 *       of the License, or (at your option) any later version.
 *
 *       This program is distributed in the hope that it will be useful,
 *       but WITHOUT ANY WARRANTY; without even the implied warranty of
 *       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *       GNU Lesser General Public License for more details.
 *
 *       You should have received a copy of the GNU Lesser General Public License
 *       along with this program; if not, write to the Free Software
 *       Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
 * 
 */
package ca.usherbrooke.agent.action;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import ca.usherbrooke.agent.SimpleMoveAgentController;
import ca.usherbrooke.application.astar.action.AStarMoveAction;
import ca.usherbrooke.application.astar.location.CompleteLocation;
import ca.usherbrooke.application.astar.location.ILocation;
import ca.usherbrooke.model.entity.IEntityModel;
import ca.usherbrooke.tools.position.Vector2D;
import ca.usherbrooke.view.slick.jade.worldView.shared.EntityViewDebug;

/**
 * @author Cody Stoutenburg
 * 
 */
public class ExplorationAction extends Action {
	private static final long serialVersionUID = 1L;

	public static List<Action> generateExplorationAction(
			SimpleMoveAgentController agent, Long maxDuration) {
		List<Action> allExplo = new ArrayList<Action>();

		List<CompleteLocation> allLocations = agent.getCompleteLocation();

		// find all location that are valid
		List<ILocation> allValidLocation = new ArrayList<ILocation>();
		for (ILocation location : allLocations) {
			if (!location.isWall() && !location.isEntity()) {
				Boolean haveWall = location.getNeighbor().size() < 8;
				for (ILocation neighbor : location.getNeighbor()) {
					if (neighbor.isWall()) {
						haveWall = true;
						break;
					}
				}
				if (haveWall) {
					Vector2D position = location.getPosition();
					if (!agent.getEntity().getSize()
							.contains(position.getX(), position.getY())) {
						allValidLocation.add(location);
					}
				}
			}
		}

		ArrayList<Float> allDistance = new ArrayList<Float>();
		ArrayList<ILocation> allClearedLocation = new ArrayList<ILocation>();
		ArrayList<Vector2D> allDirection = new ArrayList<Vector2D>();

		Vector2D position = agent.getClosestCompleteLocation(
				agent.getEntity().getCenterPosition()).getPosition();
		for (ILocation location : allValidLocation) {
			Vector2D direction = location.getPosition().sub(position);
			Vector2D normalizedDirection = direction.clone();
			normalizedDirection.normalize();

			int idx = allDirection.indexOf(normalizedDirection);
			if (idx < 0) {
				allDirection.add(normalizedDirection);
				allClearedLocation.add(location);
				allDistance.add(direction.getMagnitude());
			} else {
				Float distance = direction.getMagnitude();
				if (allDistance.get(idx) < distance) {
					allClearedLocation.set(idx, location);
					allDistance.set(idx, distance);
				}
			}
		}

		for (ILocation location : allClearedLocation) {
			ExplorationAction explo = new ExplorationAction(
					location.getPosition(), maxDuration);
			if (explo.init(agent, Collections.<Float> emptyList())
					&& explo.canDoAction()) {
				allExplo.add(explo);
			}
		}

		return allExplo;
	}

	private final Vector2D destination;
	private Boolean canDoAction;

	public ExplorationAction(Vector2D destination, Long duration) {
		super(duration);
		this.destination = destination;
		canDoAction = true;
	}

	@Override
	public Boolean init(List<Float> vals) {
		return true;
	}

	@Override
	protected Boolean canDoAction() {
		// we do not verify here because it cost a lot to verify and we may have
		// a lot of action of this kind to test, so we will choose an action and
		// we will make it finish shortly
		return true;
	}

	@Override
	public void apply() {
		Long guid = this.agentController.getEntity().getGuid();

		EntityViewDebug.INSTANCE.setTargetPosition(guid, destination);

		canDoAction = Action.canReachTargetPosition(agentController,
				destination);
		if (canDoAction) {
			List<AStarMoveAction> plan = Action.generatePlanToTargetPosition(
					this.agentController, destination);
			System.out.println("plan generated: " + plan);

			this.agentController.setPlan(plan);
		}
	}

	@Override
	public Boolean isFinish() {
		IEntityModel entity = this.agentController.getEntity();

		ILocation loc = this.agentController
				.getClosestCompleteLocation(destination);
		// we cannot reach destination
		if (loc.isWall()) {
			return true;
		}

		if (!canDoAction) {
			return true;
		}

		if (entity.getSize().contains(destination.getX(), destination.getY())) {
			return true;
		}

		return false;
	}

	@Override
	public String toString() {
		return "explore to " + destination;
	}

	@Override
	public Float generateEstimatedCost() {
		return this.agentController.getEntity().getCenterPosition()
				.getDistance(destination);
	}

	@Override
	public Float generateEstimatedWon() {
		Float estimate = 1.0f;
		// more we have a chance to search in unknow area more we won
		CompleteLocation destinationLoc = agentController
				.getClosestCompleteLocation(destination);

		// find if it is in a dead end
		ArrayList<ILocation> allNeighbor = destinationLoc.getNeighbor();
		Integer neighborCount = allNeighbor.size();
		Integer neighborWallCount = 0;
		for (ILocation neighbor : allNeighbor) {
			if (neighbor.isWall()) {
				++neighborWallCount;
			}
		}

		// it's a dead end
		if (neighborWallCount >= neighborCount - 1) {
			estimate = 1.0f;
		} else if (neighborCount < 8) {
			estimate = (float) Math.pow((8f - (neighborCount)), 4);
		} else {
			estimate = 0f;
		}

		return estimate;
	}

	@Override
	public Boolean canMemorize() {
		return false;
	}
}
